/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is js-ctypes.
 *
 * The Initial Developer of the Original Code is
 * The Mozilla Foundation <http://www.mozilla.org/>.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Fredrik Larsson <nossralf@gmail.com>
 *  Mark Finkle <mark.finkle@gmail.com>, <mfinkle@mozilla.com>
 *  Dan Witte <dwitte@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

Components.utils.import("resource://gre/modules/ctypes.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

const CTYPES_TEST_LIB = "libjsctypes-test.dylib";

function do_check_throws(f, type, stack)
{
  if (!stack)
    stack = Components.stack.caller;

  try {
    f();
  } catch (exc) {
    if (exc instanceof type)
      return;
    do_throw("expected " + type.name + " exception, caught " + exc, stack);
  }
  do_throw("expected " + type.name + " exception, none thrown", stack);
}

function run_test()
{
  var libfile = do_get_file(CTYPES_TEST_LIB);

  // open the library with an nsILocalFile
  var library = ctypes.open(libfile);

  run_void_tests(library);
  run_int8_tests(library);
  run_int16_tests(library);
  run_int32_tests(library);
  run_int64_tests(library);
  run_float_tests(library);
  run_double_tests(library);
  run_string_tests(library);
  run_mixed_tests(library);

  // test the string version of ctypes.open() as well
  var libpath = libfile.path;
  library = ctypes.open(libpath);
  run_void_tests(library);

  // test library.close
  var test_v = library.declare("test_v", ctypes.default_abi, ctypes.void_t);
  library.close();
  do_check_throws(function () { test_v(); }, Error);
  do_check_throws(function () { library.declare("test_v", ctypes.default_abi, ctypes.void_t); }, Error);

  // test that library functions throw when bound to other objects
  library = ctypes.open(libpath);
  let obj = {};
  obj.declare = library.declare;
  do_check_throws(function () { obj.declare("test_v", ctypes.default_abi, ctypes.void_t); }, Error);
  obj.close = library.close;
  do_check_throws(function () { obj.close(); }, Error);

  // test that functions work as properties of other objects
  var test_i8 = library.declare("test_i8", ctypes.default_abi, ctypes.int8_t);
  do_check_eq(test_i8(), 123);
  obj.t = test_i8;
  do_check_eq(test_i8(), 123);
  do_check_eq(obj.t(), 123);

  // bug 521937
  do_check_throws(function () { var nolib = ctypes.open("notfoundlibrary.dll"); nolib.close(); }, Error);

  // bug 522360
  do_check_eq(run_load_system_library(), true);
}

function run_void_tests(library) {
  var test_v = library.declare("test_v", ctypes.default_abi, ctypes.void_t);
  do_check_eq(test_v(), undefined);
}

function run_int8_tests(library) {
  var test_i8 = library.declare("test_i8", ctypes.default_abi, ctypes.int8_t);
  do_check_eq(test_i8(), 123);

  var test_i8_i8 = library.declare("test_i8_i8", ctypes.default_abi, ctypes.int8_t, ctypes.int8_t);
  do_check_eq(test_i8_i8(5), 5);
  do_check_eq(test_i8_i8(0), 0);
  do_check_eq(test_i8_i8(0x7f), 0x7f);
  do_check_eq(test_i8_i8(-0x80), -0x80);
  do_check_eq(1/test_i8_i8(-0), 1/0);  // that is, test_i8_i8(-0) is +0
  do_check_eq(test_i8_i8(true), 1);
  do_check_eq(test_i8_i8(false), 0);

  // don't convert anything else to an int8
  var vals = [0x80, -0x81, 0x100000000, Infinity, -Infinity, NaN,
              null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_i8_i8(vals[i]); }, TypeError);

  var test_i8_i8_sum = library.declare("test_i8_i8_sum", ctypes.default_abi, ctypes.int8_t, ctypes.int8_t, ctypes.int8_t);
  do_check_eq(test_i8_i8_sum(5, 5), 10);

  // test the range of unsigned. (we can reuse the signed C function
  // here, since it's binary-compatible.)
  var test_ui8_ui8 = library.declare("test_i8_i8", ctypes.default_abi, ctypes.uint8_t, ctypes.uint8_t);
  do_check_eq(test_ui8_ui8(0xff), 0xff);
  do_check_throws(function () { test_ui8_ui8(0x100); }, TypeError);
  do_check_throws(function () { test_ui8_ui8(-1); }, TypeError);
}

function run_int16_tests(library) {
  var test_i16 = library.declare("test_i16", ctypes.default_abi, ctypes.int16_t);
  do_check_eq(test_i16(), 12345);

  var test_i16_i16 = library.declare("test_i16_i16", ctypes.default_abi, ctypes.int16_t, ctypes.int16_t);
  do_check_eq(test_i16_i16(5), 5);
  do_check_eq(test_i16_i16(0), 0);
  do_check_eq(test_i16_i16(0x7fff), 0x7fff);
  do_check_eq(test_i16_i16(-0x8000), -0x8000);
  do_check_eq(1/test_i16_i16(-0), 1/0);  // that is, test_i16_i16(-0) is +0
  do_check_eq(test_i16_i16(true), 1);
  do_check_eq(test_i16_i16(false), 0);

  // don't convert anything else to an int16
  var vals = [0x8000, -0x8001, 0x100000000, Infinity, -Infinity, NaN,
              null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_i16_i16(vals[i]); }, TypeError);

  var test_i16_i16_sum = library.declare("test_i16_i16_sum", ctypes.default_abi, ctypes.int16_t, ctypes.int16_t, ctypes.int16_t);
  do_check_eq(test_i16_i16_sum(5, 5), 10);

  // test the range of unsigned. (we can reuse the signed C function
  // here, since it's binary-compatible.)
  var test_ui16_ui16 = library.declare("test_i16_i16", ctypes.default_abi, ctypes.uint16_t, ctypes.uint16_t);
  do_check_eq(test_ui16_ui16(0xffff), 0xffff);
  do_check_throws(function () { test_ui16_ui16(0x10000); }, TypeError);
  do_check_throws(function () { test_ui16_ui16(-1); }, TypeError);
}

function run_int32_tests(library) {
  var test_i32 = library.declare("test_i32", ctypes.default_abi, ctypes.int32_t);
  do_check_eq(test_i32(), 123456789);

  var test_i32_i32 = library.declare("test_i32_i32", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t);
  do_check_eq(test_i32_i32(5), 5);
  do_check_eq(test_i32_i32(0), 0);
  do_check_eq(test_i32_i32(0x7fffffff), 0x7fffffff);
  do_check_eq(test_i32_i32(-0x80000000), -0x80000000);
  do_check_eq(1/test_i32_i32(-0), 1/0);  // that is, test_i32_i32(-0) is +0
  do_check_eq(test_i32_i32(true), 1);
  do_check_eq(test_i32_i32(false), 0);

  // don't convert anything else to an int32
  var vals = [0x80000000, -0x80000001, Infinity, -Infinity, NaN,
              null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_i32_i32(vals[i]); }, TypeError);

  var test_i32_i32_sum = library.declare("test_i32_i32_sum", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t);
  do_check_eq(test_i32_i32_sum(5, 5), 10);

  // test the range of unsigned. (we can reuse the signed C function
  // here, since it's binary-compatible.)
  var test_ui32_ui32 = library.declare("test_i32_i32", ctypes.default_abi, ctypes.uint32_t, ctypes.uint32_t);
  do_check_eq(test_ui32_ui32(0xffffffff), 0xffffffff);
  do_check_throws(function () { test_ui32_ui32(0x100000000); }, TypeError);
  do_check_throws(function () { test_ui32_ui32(-1); }, TypeError);
}

function run_int64_tests(library) {
  var test_i64 = library.declare("test_i64", ctypes.default_abi, ctypes.int64_t);
  // JS represents 64 bit ints as doubles, so we have to be careful how many
  // significant digits we use
  do_check_eq(test_i64(), 0x28590a1c921de000);

  var test_i64_i64 = library.declare("test_i64_i64", ctypes.default_abi, ctypes.int64_t, ctypes.int64_t);
  do_check_eq(test_i64_i64(5), 5);
  do_check_eq(test_i64_i64(0), 0);
  do_check_eq(test_i64_i64(0x7ffffffffffff000), 0x7ffffffffffff000);
  do_check_eq(test_i64_i64(-0x8000000000000000), -0x8000000000000000);
  do_check_eq(1/test_i64_i64(-0), 1/0);  // that is, test_i64_i64(-0) is +0
  do_check_eq(test_i64_i64(true), 1);
  do_check_eq(test_i64_i64(false), 0);

  // don't convert anything else to an int64
  var vals = [0x8000000000000000, -0x8000000000001000, Infinity, -Infinity, NaN,
              null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_i64_i64(vals[i]); }, TypeError);

  var test_i64_i64_sum = library.declare("test_i64_i64_sum", ctypes.default_abi, ctypes.int64_t, ctypes.int64_t, ctypes.int64_t);
  do_check_eq(test_i64_i64_sum(5, 5), 10);

  // test the range of unsigned. (we can reuse the signed C function
  // here, since it's binary-compatible.)
  var test_ui64_ui64 = library.declare("test_i64_i64", ctypes.default_abi, ctypes.uint64_t, ctypes.uint64_t);
  do_check_eq(test_ui64_ui64(0xfffffffffffff000), 0xfffffffffffff000);
  do_check_throws(function () { test_ui64_ui64(0x10000000000000000); }, TypeError);
  do_check_throws(function () { test_ui64_ui64(-1); }, TypeError);
}

function run_float_tests(library) {
  var test_f = library.declare("test_f", ctypes.default_abi, ctypes.float);
  do_check_eq(test_f(), 123456.5);

  var test_f_f = library.declare("test_f_f", ctypes.default_abi, ctypes.float, ctypes.float);
  do_check_eq(test_f_f(5), 5);
  do_check_eq(test_f_f(5.25), 5.25);
  do_check_eq(test_f_f(Infinity), Infinity);
  do_check_eq(test_f_f(-Infinity), -Infinity);
  do_check_eq(isNaN(test_f_f(NaN)), true);
  do_check_eq(1/test_f_f(-0), 1/-0); // that is, test_f_f(-0) is -0

  // allow values that can't be represented precisely as a float
  do_check_eq(test_f_f(1 + 1/0x80000000), 1);

  // don't convert anything else to a float
  var vals = [true, false, null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_f_f(vals[i]); }, TypeError);

  var test_f_f_sum = library.declare("test_f_f_sum", ctypes.default_abi, ctypes.float, ctypes.float, ctypes.float);
  do_check_eq(test_f_f_sum(5, 5), 10);
  do_check_eq(test_f_f_sum(5.5, 5.5), 11);
}

function run_double_tests(library) {
  var test_d = library.declare("test_d", ctypes.default_abi, ctypes.double);
  do_check_eq(test_d(), 1234567890123456789.5);

  var test_d_d = library.declare("test_d_d", ctypes.default_abi, ctypes.double, ctypes.double);
  do_check_eq(test_d_d(5), 5);
  do_check_eq(test_d_d(5.25), 5.25);
  do_check_eq(test_d_d(Infinity), Infinity);
  do_check_eq(test_d_d(-Infinity), -Infinity);
  do_check_eq(isNaN(test_d_d(NaN)), true);
  do_check_eq(1/test_d_d(-0), 1/-0); // that is, test_d_d(-0) is -0

  // don't convert anything else to a double
  var vals = [true, false, null, undefined, "", "0", {}, [], new Number(16),
              {toString: function () { return 7; }},
              {valueOf: function () { return 7; }}];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function () { test_d_d(vals[i]); }, TypeError);

  var test_d_d_sum = library.declare("test_d_d_sum", ctypes.default_abi, ctypes.double, ctypes.double, ctypes.double);
  do_check_eq(test_d_d_sum(5, 5), 10);
  do_check_eq(test_d_d_sum(5.5, 5.5), 11);
}

function run_string_tests(library) {
  var test_ansi_len = library.declare("test_ansi_len", ctypes.default_abi, ctypes.int32_t, ctypes.string);
  do_check_eq(test_ansi_len(""), 0);
  do_check_eq(test_ansi_len("hello world"), 11);

  // don't convert anything else to a string
  var vals = [true, 0, 1/3, undefined, {}, {toString: function () { return "bad"; }}, []];
  for (var i = 0; i < vals.length; i++)
    do_check_throws(function() { test_ansi_len(vals[i]); }, TypeError);

  var test_wide_len = library.declare("test_wide_len", ctypes.default_abi, ctypes.int32_t, ctypes.ustring);
  do_check_eq(test_wide_len("hello world"), 11);

  var test_ansi_ret = library.declare("test_ansi_ret", ctypes.default_abi, ctypes.string);
  do_check_eq(test_ansi_ret(), "success");

  var test_wide_ret = library.declare("test_wide_ret", ctypes.default_abi, ctypes.ustring);
  do_check_eq(test_wide_ret(), "success");

  var test_ansi_echo = library.declare("test_ansi_echo", ctypes.default_abi, ctypes.string, ctypes.string);
  do_check_eq(test_ansi_echo("anybody in there?"), "anybody in there?");
  do_check_eq(test_ansi_echo(null), null);
}

function run_mixed_tests(library) {
  var test_floor = library.declare("test_floor", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t, ctypes.float);
  do_check_eq(test_floor(5, 5.5), 10);
  do_check_throws(function() { test_floor(5.5, 5); }, TypeError);
}

// bug 522360 - try loading system library without full path
function run_load_system_library()
{
//@line 335 "/builds/moz2_slave/macosx_build/build/js/ctypes/tests/unit/test_jsctypes.js.in"
  var syslib = ctypes.open("libm.dylib");
//@line 343 "/builds/moz2_slave/macosx_build/build/js/ctypes/tests/unit/test_jsctypes.js.in"
  syslib.close();
  return true;
}
